package com.ecar.api.oauth;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import java.io.IOException;

/**
 * Created by admin on 2016/12/23.
 */
public class WebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {

	private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

	public WebResponseExceptionTranslator() {
	}

	@Override
	public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
		// @formatter:off
		Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
		OAuth2Exception ase = (OAuth2Exception)
			throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
		if (ase != null) {
			return handleOAuth2Exception(ase);
		} else {
			AuthenticationException ase1 = (AuthenticationException)
				throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (ase1 != null) {
				return handleOAuth2Exception(
					new WebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e));
			} else {
				AccessDeniedException ase2 = (AccessDeniedException)
					throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
				if (ase2 instanceof AccessDeniedException) {
					return handleOAuth2Exception(
						new WebResponseExceptionTranslator.ForbiddenException(ase2.getMessage(), ase2));
				} else {
					HttpRequestMethodNotSupportedException ase3 = (HttpRequestMethodNotSupportedException)
						throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);

					if (ase3 instanceof HttpRequestMethodNotSupportedException) {
						return handleOAuth2Exception(
							new WebResponseExceptionTranslator.MethodNotAllowed(ase3.getMessage(), ase3));
					} else {
						return handleOAuth2Exception(
							new WebResponseExceptionTranslator.ServerErrorException(e.getMessage(), e));
					}
				}
			}
		}
		// @formatter:on
	}

	private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {
		int status = e.getHttpErrorCode();
		HttpHeaders headers = new HttpHeaders();
		headers.set("Cache-Control", "no-store");
		headers.set("Pragma", "no-cache");
		if (status == HttpStatus.UNAUTHORIZED.value() || e instanceof InsufficientScopeException) {
			headers.set("WWW-Authenticate", String.format("%s %s", "Bearer", e.getSummary()));
		}

		return new ResponseEntity(e, headers, HttpStatus.valueOf(status));
	}

	public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
		this.throwableAnalyzer = throwableAnalyzer;
	}

	private static class MethodNotAllowed extends OAuth2Exception {
		public MethodNotAllowed(String msg, Throwable t) {
			super(msg, t);
		}

		public String getOAuth2ErrorCode() {
			return "method_not_allowed";
		}

		public int getHttpErrorCode() {
			return 405;
		}
	}

	private static class UnauthorizedException extends OAuth2Exception {
		public UnauthorizedException(String msg, Throwable t) {
			super(msg, t);
		}

		public String getOAuth2ErrorCode() {
			return "unauthorized";
		}

		public int getHttpErrorCode() {
			return 401;
		}
	}

	private static class ServerErrorException extends OAuth2Exception {
		public ServerErrorException(String msg, Throwable t) {
			super(msg, t);
		}

		public String getOAuth2ErrorCode() {
			return "server_error";
		}

		public int getHttpErrorCode() {
			return 500;
		}
	}

	private static class ForbiddenException extends OAuth2Exception {
		public ForbiddenException(String msg, Throwable t) {
			super(msg, t);
		}

		public String getOAuth2ErrorCode() {
			return "access_denied";
		}

		public int getHttpErrorCode() {
			return 403;
		}
	}

}
